/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * system & local includes:
 **/
#include "../commonheaders.h"
#include "svcExImport.h"
#include "classExImContactBase.h"

/**
 * Miranda includes
 **/
#include <m_protocols.h>
#include <m_protosvc.h>

namespace NExImport {
namespace NIniFile {

/***********************************************************************************************************
 * exporting stuff
 ***********************************************************************************************************/

/**
 * name:	ExportModule
 * desc:	write all settings from a database module to file
 * param:	hContact	- handle of contact the module is owned from
 *			szModule	- name of the module to save
 *			file		- file to write the settings to
 * return	nothing
 **/
static VOID ExportModule( HANDLE hContact, LPCSTR szModule, FILE* file )
{
	DB::CEnumList	Settings;

	if( !Settings.EnumSettings( hContact, szModule ) )
	{
		DBVARIANT dbv;
		LPSTR here;
		WORD j;
		INT i;
		LPSTR szSetting;

		// print the module header..
		fprintf( file, "[%s]\n", szModule );

		for( i = 0; i < Settings.getCount(); i++ )
		{
			szSetting = Settings[i];

			if( !DB::Setting::GetAsIs( hContact, szModule, szSetting, &dbv ) )
			{
				switch( dbv.type ) 
				{
					case DBVT_BYTE:
						{
							fprintf( file, "%s=b%u\n", szSetting, dbv.bVal );
						}
						break;

					case DBVT_WORD:
						{
							fprintf( file, "%s=w%u\n", szSetting, dbv.wVal );
						}
						break;

					case DBVT_DWORD:
						{
							fprintf( file, "%s=d%u\n", szSetting, dbv.dVal );
						}
						break;

					case DBVT_UTF8:
					case DBVT_ASCIIZ:
						{
							for( here = dbv.pszVal; here && *here; here++ ) 
							{
								switch( *here ) 
								{
									// convert \r to STX 
									case '\r':
										*here = 2;
										break;

									// convert \n to ETX
									case '\n':
										*here = 3;
								}
							}
							if( dbv.type == DBVT_UTF8 ) 
							{
								fprintf( file, "%s=u%s\n", szSetting, dbv.pszVal );
							}
							else
							{
								fprintf( file, "%s=s%s\n", szSetting, dbv.pszVal );
							}
						}
						break;

					case DBVT_BLOB:
						{
							fprintf( file, "%s=B", szSetting );
							for( j = 0; j < dbv.cpbVal; j++ )
							{
								fprintf( file, "%02X ", ( BYTE )dbv.pbVal[j] );
							}
							fputc( '\n', file );
						}
						break;
				}
				DB::Variant::Free( &dbv );
			}
		}
	}
	fprintf( file, "\n" );
}

/**
 * name:	ExportContact
 * desc:	Exports a certain contact to an ini file.
 * param:	hContact	- contact to export or -1 to export all contacts
 *			pModules	- module to export, NULL to export all modules of a contact
 *			file		- ini file to write the contact to
 **/
static BOOLEAN ExportContact( HANDLE hContact, DB::CEnumList* pModules, FILE* file )
{
	CExImContactBase vcc;

	if( pModules ) 
	{
		if( ( vcc = hContact ) != NULL ) 
		{
			INT i;
			LPSTR p;

			vcc.toIni( file );

			for( i = 0; i < pModules->getCount(); i++ )
			{
				p = ( *pModules )[i];

				if( mir_stricmp( p, "Protocol" ) )
				{
					ExportModule( hContact, p, file );
				}
			}
			return TRUE;
		}
	}
	return FALSE;
}

/**
 * name:	Export
 * desc:	Exports a certain contact or all contacts to an ini file.
 * param:	hContact	- contact to export or -1 to export all contacts
 *			pszFileName	- ini-filename to write the contact to
 **/
INT Export( HANDLE hContact, LPCSTR pszFileName ) // hContact == -1 export entire db. module == NULL export entire contact.
{
	FILE* file;
	DB::CEnumList Modules;

	if( !SelectModulesToExport( hContact, &Modules, NULL ) ) 
	{
		if( ( file = fopen( pszFileName, "wt") ) == NULL ) 
		{
			MsgErr( NULL, 
				LPGENT( "The ini-file \"%s\"\nfor saving contact information could not be opened." ),
				pszFileName );
			return 1;
		}
		
		SetCursor( LoadCursor( NULL, IDC_WAIT ) );

		if( Modules.getCount() == 0 )
		{
			Modules.EnumModules();
		}

		if( hContact == INVALID_HANDLE_VALUE ) 
		{
			for( hContact = DB::Contact::FindFirst();
					 hContact != NULL;
					 hContact = DB::Contact::FindNext( hContact ) )
			{
				ExportContact( hContact, &Modules, file );
			}
		}
		else 
		{
			ExportContact( hContact, &Modules, file );
		}
		fclose( file );
		SetCursor( LoadCursor( NULL, IDC_ARROW ) );
	}
	return 0;
}

/***********************************************************************************************************
 * importing stuff
 ***********************************************************************************************************/

LPSTR strnrchr( LPSTR string, INT ch, DWORD len )
{
    LPSTR start = ( LPSTR )string;

    string += len;                       /* find end of string */
                                            /* search towards front */
    while ( --string != start && *string != ( CHAR )ch );
    if( *string == ( CHAR )ch )                /* char found ? */
            return ( ( LPSTR )string );
    return( NULL );
}

/**
 * name:	ImportreadLine
 * desc:	read exactly one line into a buffer and return its pointer. Size of buffer is managed.
 * param:	file	- pointer to a file
 *			string	- the string to write the read line to
 * return:	pointer to the buffer on success or NULL on error
 **/
static DWORD ImportreadLine( FILE* file, LPSTR &str )
{
	CHAR c;
	DWORD l = 0;
	BOOLEAN bComment = 0;

	str[0] = 0;
	while( !feof( file ) ) {
		switch( c = fgetc( file ) ) {
			case EOF:
				// reading error
				if( ferror( file ) ) {
					mir_free(str);
					return 0;
				}
				// end of line & file
				return l;
			
			case '\r':
			case '\n':
				// ignore empty lines
				if( l == 0 ) { 
					bComment = 0;
					continue;
				}
				return l;
			
			case ';':
				// found a comment line
				bComment |= l == 0;
			case '\t':
			case ' ':
				if( l == 0 ) break;		// ignore space and tab at the beginning of the line
			
			default:
				if( !bComment ) {
					str = mir_strncat_c(str, c);
					l++;
				}
				break;
		}
	}
	return 0;
}

/**
 * name:	ImportFindContact
 * desc:	This function decodes the given line, which is already identified to be a contact line.
 *			The resulting information is matcht to the given hContact if it isn't NULL.
 *			Otherwise all existing contacts are matched.
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			pszBuf		- pointer to the buffer holding the string of the current line in the ini.-file
 *			cchBuf		- character count of the buffer
 * return:	handle to the contact that matches the information or NULL if no match
 **/


static HANDLE ImportFindContact( HANDLE hContact, LPSTR &strBuf, BOOLEAN bCanCreate )
{
	CExImContactBase vcc;

	vcc.fromIni( strBuf );
	if( hContact != INVALID_HANDLE_VALUE ) {
		if( vcc.isHandle( hContact ) )
			return hContact;
	}
	else if( bCanCreate )	
		return vcc.toDB();
	
	return vcc.handle();
}

/**
 * name:	ImportSetting
 * desc:	This function writes a line identified as a setting to the database
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			szModule	- module to write the setting to
 *			strLine		- string with the setting and its value to write to db
 * return:	0 if writing was ok, 1 otherwise
 **/
INT ImportSetting( HANDLE hContact, LPCSTR szModule, LPSTR &strLine )
{
	DBCONTACTWRITESETTING cws;
	LPSTR end, value;
	DWORD numLines = 0;
	DWORD brk;
	LPSTR pszLine = ( LPSTR )strLine[0];

	if( !szModule || !*szModule ) return 1;
	if( ( end = value = mir_strchr( pszLine, '=') ) == NULL ) return 1;

	// truncate setting string if it has spaces at the end
	do { 
		if( end == pszLine )
			return 1;
		*( end-- ) = 0; 
	} while( *end == '\t' || *end == ' ' || *end < 27 );

	cws.szModule = szModule;
	cws.szSetting = pszLine;

	// skip spaces from the beginning of the value
	do { 
		value++; 
		// if the value is empty, delete it from db
		if( *value == '\0' )
			return DB::Setting::Delete( hContact, szModule, pszLine );
	} while( *value == '\t' || *value == ' ' );

	// decode database type and value
	switch( *( value++ ) ) {
		case 'b':
			if( brk = strspn( value, "0123456789-") )
				*( value + brk ) = 0;
			cws.value.type = DBVT_BYTE;
			cws.value.bVal = ( BYTE )atoi( value );
			break;
		case 'w':
			if( brk = strspn( value, "0123456789-") )
				*( value + brk ) = 0;
			cws.value.type = DBVT_WORD;
			cws.value.wVal = ( WORD )atoi( value );
			break;
		case 'd':
			if( brk = strspn( value, "0123456789-") )
				*( value + brk ) = 0;
			cws.value.type = DBVT_DWORD;
			cws.value.dVal = ( DWORD )atoi( value );
			break;
		case 's':
		case 'u':
			cws.value.type = *( value - 1 ) == 's' ? DBVT_ASCIIZ : DBVT_UTF8;
			cws.value.pszVal = value;
			
			for( end = value; end && *end; end++ ) {
				switch( *end ) {
					// convert STX back to \r
					case 2:
						*end = '\r';
						break;
					// convert ETX back to \n
					case 3:
						*end = '\n';
						break;
				}
			}
			break;
		
		case 'B':
		{
			PBYTE dest;

			cws.value.type = DBVT_BLOB;
			cws.value.cpbVal = ( WORD )mir_strlen( value ) / 3;
			cws.value.pbVal = ( PBYTE )value;
			for( dest = cws.value.pbVal, value = strtok( value, " " ); value && *value; value = strtok( NULL, " ") )
				*( dest++ ) = ( BYTE )strtol( value, NULL, 16 );
			*dest = 0;
			break;
		}
		case 'U':
			cws.value.type = DBVT_UTF8;
			cws.value.pszVal = value;
			for( end = cws.value.pszVal, value = strtok( value, " " ); value && *value; value = strtok( NULL, " ") )
				*( end++ ) = ( BYTE )strtol( value, NULL, 16 );
			*end = 0;
			break;
		
		default:
			cws.value.type = DBVT_DELETED;
			return 1;
	}
	return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM )hContact, ( LPARAM )&cws );
}

/**
 * name:	Import
 * desc:	This function imports an ini file
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			file		- module to write the setting to
 *			strLine		- string with the setting and its value to write to db
 * return:	0 if writing was ok, 1 otherwise
 **/
INT Import( HANDLE hContact, LPCSTR pszFileName )
{
	FILE* file;
	HANDLE hNewContact = INVALID_HANDLE_VALUE;
	DWORD end, numLines = 0;
	CHAR szModule[MAXSETTING] = {0};
	WORD numContactsInFile = 0,		// number of contacts in the inifile
		 numContactsAdded = 0;		// number of contacts, that were added to the database
	CHAR *strBuf; strBuf = (CHAR *) mir_alloc(1); *strBuf = 0;
	
	if( file = fopen( pszFileName, "rt") ) {
		SetCursor( LoadCursor( NULL, IDC_WAIT ) );

		while( ImportreadLine( file, strBuf ) ) {
			numLines++;

			// contact was found and imported
			if( hContact != INVALID_HANDLE_VALUE && hNewContact != INVALID_HANDLE_VALUE )
				break;

			if( hContact ) {
				// there are some modules of a contact
				if( !strncmp( strBuf, "FROM CONTACT:", 13 ) ) {
					strBuf = mir_strnerase( strBuf, 0, 13 );
					while( strBuf[0] == ' ' || strBuf[0] == '\t' )
						strBuf = mir_strnerase( strBuf, 0, 1 );

					numContactsInFile++;
					if( ( hNewContact = ImportFindContact( hContact, strBuf, FALSE ) ) != INVALID_HANDLE_VALUE )
						numContactsAdded++;
					continue;
				}
			}
			else {
				// importing settings is only valid vor the main menu item
				if( !strncmp( strBuf, "SETTINGS:", 9 ) ) {
					*szModule = 0;
					hNewContact = NULL;
					continue;
				}
			}

			// there is a contact to add
			if( !strncmp( strBuf, "CONTACT:", 8 ) ) {
				strBuf = mir_strnerase( strBuf, 0, 8 );
				while( strBuf[0] == ' ' || strBuf[0] == '\t' )
					strBuf = mir_strnerase( strBuf, 0, 1 );

				*szModule = 0;
				numContactsInFile++;			
				if( ( hNewContact = ImportFindContact( hContact, strBuf, TRUE ) ) != INVALID_HANDLE_VALUE )
					numContactsAdded++;
				continue;
			}

			// read modules and settings only for valid contacts
			if( hNewContact != INVALID_HANDLE_VALUE ) {
				// found a module line
				if( strBuf[0] == '[' && ( end = (strchr(strBuf, ']') - strBuf + 1) ) > 0 ) {
					mir_strncpy( szModule, &strBuf[1], end );
					continue;
				}
				// try to import a setting
				ImportSetting( hNewContact, szModule, strBuf );
			}
		}
		fclose( file );
		SetCursor( LoadCursor( NULL, IDC_ARROW ) );

		if( hContact ) {
			// the contact was not found in the file
			if( numContactsInFile > 0 && !numContactsAdded ) {
				MsgErr( NULL,
					LPGENT( "None of the %d contacts, stored in the ini-file, match the selected contact!\nNothing will be imported" ),
					numContactsInFile );
			}
		}
		else {
			MsgBox( NULL, MB_ICON_INFO, LPGENT( "Import complete" ), LPGENT( "Some basic statistics" ),
				LPGENT( "Added %d of %d contacts stored in the ini-file." ),
				numContactsAdded, numContactsInFile );
		}
		return 0;
	}
	MsgErr( NULL, 
		LPGENT( "The ini-file \"%s\"\nfor reading contact information could not be opened." ),
		pszFileName );
	return 1;
}

} // namespace NIniFile
} // namespace NExImport
